package com.video.media.service.impl;

import com.alibaba.nacos.common.utils.IoUtils;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.video.base.exception.VideoException;
import com.video.base.model.PageParams;
import com.video.base.model.PageResult;
import com.video.base.model.RestResponse;
import com.video.media.mapper.MediaFilesMapper;
import com.video.media.model.dto.QueryMediaParamsDto;
import com.video.media.model.dto.UploadFileParamsDto;
import com.video.media.model.dto.UploadFileResultDto;
import com.video.media.model.po.MediaFiles;
import com.video.media.service.MediaFileService;
import io.minio.*;
import io.minio.errors.*;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;


/**
 * @description TODO
 * @author Mr.M
 * @date 2022/9/10 8:58
 * @version 1.0
 */

@Slf4j
@Service
public class MediaFileServiceImpl implements MediaFileService {

 @Autowired
 MediaFilesMapper mediaFilesMapper;


 @Autowired
 MinioClient minioClient;


 /**
  * 普通文件
  */
 @Value("${minio.bucket.files}")
 private String bucket_mediafiles;


 /**
  * 视频
  */
 @Value("${minio.bucket.videofiles}")
 private String bucket_video;

 @Override
 public PageResult<MediaFiles> queryMediaFiels(Long companyId, PageParams pageParams, QueryMediaParamsDto queryMediaParamsDto) {

  //构建查询条件对象
  LambdaQueryWrapper<MediaFiles> queryWrapper = new LambdaQueryWrapper<>();
  
  //分页对象
  Page<MediaFiles> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
  // 查询数据内容获得结果
  Page<MediaFiles> pageResult = mediaFilesMapper.selectPage(page, queryWrapper);
  // 获取数据列表
  List<MediaFiles> list = pageResult.getRecords();
  // 获取数据总数
  long total = pageResult.getTotal();
  // 构建结果集
  PageResult<MediaFiles> mediaListResult = new PageResult<>(list, total, pageParams.getPageNo(), pageParams.getPageSize());
  return mediaListResult;

 }







 /**
  * 根据扩展名获取mimeType
  */
 public String getMimeType(String extension){
  if(extension == null){
   extension = "";
  }
  ContentInfo contentInfo = ContentInfoUtil.findMimeTypeMatch(extension);
  String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用字节流
  if(contentInfo != null){
    mimeType = contentInfo.getMimeType();
  }
  return mimeType;
 }



 /**
  * 将文件上传到minio
  */
 public boolean addMediaFilesToMinIO(String localFilePath ,String mimeType , String  bucket ,String objectName) {
  try {
   UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
           .bucket(bucket)//桶
           .filename(localFilePath)//本地文件路径
           .object(objectName)  //对象名 //放在子目录下
           .contentType(mimeType)  //文件类型
           .build();
   minioClient.uploadObject(uploadObjectArgs);
   return true;
  } catch (Exception e) {
    log.error("上传文件出错");
  }
   return  false;
 }



 /**
  * 自动生成目录
**/
 private String getFileFolder() {
  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
  String folder = dateFormat.format(new Date()).replace("-","/") + "/";
  return folder;
 }


 /**
  * 获取文件的Md5值
  */
 private String getFileMd5(File file){
  try {
   FileInputStream fileInputStream = new FileInputStream(file);
   String fileMd5 = DigestUtils.md5Hex(fileInputStream);
   return fileMd5;
  } catch (IOException e) {
    log.error(e.getMessage());
    return null;
  }
 }


 /**
  * 上传文件信息
  * @param companyId  机构id
  * @param fileMd5   文件id
  * @param uploadFileParamsDto
  * @param bucKet  桶
  * @param objectName  文件名
  * @return
  */
 @Transactional
 public  MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,
                                      UploadFileParamsDto uploadFileParamsDto,String bucKet,String objectName){
  MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
  if(mediaFiles ==null){
   mediaFiles = new MediaFiles();
   BeanUtils.copyProperties(uploadFileParamsDto,mediaFiles);
   //文件id
   mediaFiles.setId(fileMd5);
   //机构id
   mediaFiles.setCompanyId(companyId);
   mediaFiles.setBucket(bucKet);
   mediaFiles.setFilePath(objectName);
   mediaFiles.setFileId(fileMd5);
   mediaFiles.setUrl("/" + bucKet + "/" + objectName);
   mediaFiles.setCreateDate(LocalDateTime.now());
   mediaFiles.setStatus("1");
   mediaFiles.setAuditStatus("002003");
   int insert = mediaFilesMapper.insert(mediaFiles);
   if(insert <= 0){
    log.debug("保存文件到数据库失败");
   }
  }
  return mediaFiles;
 }

 /**
  * @description 上传文件的通用接口
  * @param companyId           机构id
  * @param uploadFileParamsDto 文件信息
  * @param localFilePath  本地文件路径
  * @return com.xuecheng.media.model.dto.UploadFileResultDto
  */
 @Override
 //@Transactional
 public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {
  String filename = uploadFileParamsDto.getFilename();
  //获取拓展名
  String extension = filename.substring(filename.lastIndexOf("."));
  String mimeType = getMimeType(extension);
  //获取目录
  String fileFolder = getFileFolder();
  //文件的md5值
  String fileMd5 = getFileMd5(new File(localFilePath));
  String objectName = fileFolder + fileMd5 + extension;
  //将文件上传到minio
  boolean b = addMediaFilesToMinIO(localFilePath, mimeType, bucket_mediafiles, objectName);
  if(!b){
   VideoException.cast("上传文件失败");
  }
  //将文件信息保存到数据库
  MediaFiles mediaFiles = addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_mediafiles, objectName);
  if(mediaFiles == null){
   VideoException.cast("入库失败");
  }
  UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
  BeanUtils.copyProperties(mediaFiles,uploadFileResultDto);
  return uploadFileResultDto;
 }

 /**
  * 检查文件
  * @param fileMd5
  * @return
  */
 @Override
 public RestResponse<Boolean> checkFile(String fileMd5) {
  MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
  // 数据库中不存在，则直接返回false 表示不存在
  if (mediaFiles != null) {
   // 若数据库中存在，根据数据库中的文件信息，则继续判断bucket中是否存在
   FilterInputStream inputStream = null;
   try {
    inputStream = minioClient.getObject(GetObjectArgs
            .builder()
            .bucket(mediaFiles.getBucket())
            .object(mediaFiles.getFilePath())
            .build());
    if (inputStream != null) {
       return RestResponse.success(true);
    }
    } catch (Exception e) {
         return RestResponse.success(false);
     }finally {
       if(inputStream != null){
        try {
         inputStream.close();
        } catch (IOException e) {
         throw new RuntimeException(e);
        }
       }
   }
  }
     return RestResponse.success(false);
 }




 /**
  * 检查分块
  * @param fileMd5
  * @param chunkIndex
  * @return
  */
 @Override
 public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex) {
  // 获取分块文件的目录
  String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
  //第几个分块
  String chunkFilePath = chunkFileFolderPath + chunkIndex;
  FilterInputStream inputStream = null;
  try {
   // 判断分块是否存在
   inputStream = minioClient.getObject(GetObjectArgs
           .builder()
           .bucket(bucket_video)
           .object(chunkFilePath)
           .build());
   // 不存在返回false
   if (inputStream !=null ) {
    return RestResponse.success(true);
   }
  } catch (Exception e) {
    // 出异常也返回false
    return RestResponse.success(false);
  }finally {
      if(inputStream != null){
       try {
        inputStream.close();
       } catch (IOException e) {
        throw new RuntimeException(e);
       }
      }
  }
  // 文件不存在
  return RestResponse.success(false);
 }


 /**
  * 获取分块文件路径
  * @param fileMd5
  * @return
  */
 private String getChunkFileFolderPath(String fileMd5) {
  return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + "chunk" + "/";
 }


 /**
  * 上传分块
  * @param fileMd5
  * @param chunk
  * @param localChunkFilePath
  * @return
  */
 @Override
 public RestResponse uploadChunk(String fileMd5, int chunk, String localChunkFilePath) {
  // 分块文件路径
  String chunkFilePath = getChunkFileFolderPath(fileMd5) + chunk;
  String mimeType = getMimeType(null);
  try {
   boolean b = addMediaFilesToMinIO(localChunkFilePath, mimeType,bucket_video, chunkFilePath);
   if(b)
   return RestResponse.success(true);
  } catch (Exception e) {
   log.debug("上传分块文件：{}失败：{}", chunkFilePath, e.getMessage());
  }
  return RestResponse.validfail("上传文件失败", false);
 }





 /**
  * 从Minio中下载文件
  * @param
  * @param bucket        桶
  * @param objectName    桶内文件路径
  * @return
  */
 private File downloadFileFromMinio(String bucket, String objectName) {
    File minioFile = null;
    FileOutputStream fileOutputStream = null;
  try {
   InputStream stream = minioClient.getObject(GetObjectArgs.builder()
                   .bucket(bucket)
                   .object(objectName)
                   .build());
   //创建临时文件
   minioFile = File.createTempFile("minio","merge");
   fileOutputStream = new FileOutputStream(minioFile);
   IoUtils.copy(stream,fileOutputStream);
   return minioFile;
  } catch (Exception e) {
      throw new RuntimeException(e);
  } finally {
     if(fileOutputStream != null){
      try {
       fileOutputStream.close();
      } catch (IOException e) {
       throw new RuntimeException(e);
      }
     }
  }
 }


 /**
  * 根据MD5和文件扩展名，生成文件路径，例 /2/f/2f6451sdg/2f6451sdg.mp4
  * @param fileMd5       文件MD5
  * @param extension     文件扩展名
  * @return
  */
 private String getFilePathByMd5(String fileMd5, String extension) {
  return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + fileMd5 + extension;
 }


 /**
  * 合并分块
  * @param companyId
  * @param fileMd5
  * @param chunkTotal
  * @param uploadFileParamsDto
  * @return
  */
 @Override
 public RestResponse mergeChunks(Long companyId, String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto)   {
  //指定原分块文件信息
  List<ComposeSource> sources = new ArrayList<>();
  //分快文件的路径
  String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
  //获取拓展名
  String filename = uploadFileParamsDto.getFilename();
  String extension= filename.substring(filename.lastIndexOf("."));
  //文件的位置
  String objectName= getFilePathByMd5(fileMd5, extension);
  try {
   //合并分块文件
   for(int i = 0 ;i < chunkTotal ; i ++) {
    ComposeSource composeSource = ComposeSource.builder()
            .bucket(bucket_video)
            .object(chunkFileFolderPath + i)
            .build();
    sources.add(composeSource);
   }
   //指定合并后的信息
   ComposeObjectArgs composeObjectArgs = ComposeObjectArgs.builder()
           .bucket(bucket_video)
           .object(objectName)
           .sources(sources)
           .build();
   minioClient.composeObject(composeObjectArgs);
  } catch (Exception e) {
     log.error("合并文件出错");
     return RestResponse.validfail("合并文件出差",false);
  }
  File file = null;
  FileInputStream fileInputStream = null;
  try {
   //校验和源文件是不是一样
   file = downloadFileFromMinio(bucket_video, objectName);
   uploadFileParamsDto.setFileSize(file.length());
    fileInputStream = new FileInputStream(file);
   String md5Hex = DigestUtils.md5Hex(fileInputStream);
   if(!fileMd5.equals(md5Hex)){
    return RestResponse.validfail("合并校验失败",false);
   }
  } catch (IOException e) {
     return RestResponse.validfail("合并校验失败",false);
  } finally {
    if(fileInputStream != null){
     try {
      fileInputStream.close();
     } catch (IOException e) {
        return RestResponse.validfail("合并校验失败",false);
     }
    }
  }

   //入库
  MediaFiles mediaFiles = addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_video, objectName);
  if(mediaFiles  == null ){
   return RestResponse.validfail("合并入库失败",false);
  }
  //清理分块文件
  return RestResponse.success();
 }


 /**
  * 查询预览文件
  * @param mediaId
  * @return
  */
 @Override
 public MediaFiles getFileById(String mediaId) {
  MediaFiles mediaFiles = mediaFilesMapper.selectById(mediaId);
  if (mediaFiles == null || StringUtils.isEmpty(mediaFiles.getUrl())) {
   VideoException.cast("视频还没有转码处理");
  }
  return mediaFiles;
 }


}
